package com.model.demo.spring.mini.mvcframework.v1;

import com.model.demo.spring.mini.mvcframework.annotation.MyAutowired;
import com.model.demo.spring.mini.mvcframework.annotation.MyController;
import com.model.demo.spring.mini.mvcframework.annotation.MyRequestMapping;
import com.model.demo.spring.mini.mvcframework.annotation.MyService;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class MyDispatcherServlet extends HttpServlet {
    private Properties properties = new Properties();

    private List<String> classNames = new ArrayList<>();

    private Map<String,Object> ioc = new HashMap<>();

    private Map<String,Method> handleMapping = new HashMap<String, Method>();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatcher(req, resp);
    }

    //根据请求路径找到handleMapping对应method,反射调用
    private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) {
        String requestURI = req.getRequestURI();
        String contextPath = req.getContextPath();
        requestURI = requestURI.replace(contextPath, "").replaceAll("/+" , "/");

        try {
            if(!handleMapping.containsKey(requestURI)){
                resp.getWriter().write("404 Not Fund");
                return;
            }
            Method method = handleMapping.get(requestURI);
            String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
            //请求参数
            Map<String, String[]> parameterMap = req.getParameterMap();
            //方法形参列表
            Class<?>[] parameterTypes = method.getParameterTypes();
            //实参列表
//            Object [] paramsValue = new Object[];
            method.invoke(ioc.get(beanName), new Object[]{req,resp,parameterMap.get("name")[0]});
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } finally {
            try {
                resp.getWriter().close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void init() throws ServletException {
        //1.加载配置文件
        doLoadConfig();
        //2.扫描相关类
        doScanPackage(properties.getProperty("scanBasePackage"));
        //3.初始化扫描到的类，放入IOC
        doInitIOC();
        //4.完成依赖注入
        doAutowired();
        //5.初始化HandleMapping
        doInitHandleMapping();
        System.out.println("init fished!!");
    }

    private void doInitHandleMapping() {
        if(ioc.isEmpty()){return;}
        for (Map.Entry<String,Object> entry:ioc.entrySet()){
            Class<?> clazz = entry.getValue().getClass();
            if(!clazz.isAnnotationPresent(MyController.class)){continue;}
            String root = "";
            if(clazz.isAnnotationPresent(MyRequestMapping.class)){
                MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
                root = annotation.value();
            }
            Method[] methods = clazz.getMethods();
            for (Method method : methods){
                if(!method.isAnnotationPresent(MyRequestMapping.class)){continue;}
                MyRequestMapping methodAnnotation = method.getAnnotation(MyRequestMapping.class);
                String path = ("/" + root + "/" + methodAnnotation.value()).replaceAll("/+" ,"/");
                handleMapping.put(path, method);
            }
        }

    }


    private void doAutowired() {
        if(ioc.isEmpty()){return;}
        for (Map.Entry<String,Object> entry:ioc.entrySet()){
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field:fields){
                if(!field.isAnnotationPresent(MyAutowired.class)){ continue; }
                //有Autowired注解才注入值
//                if(field.isAnnotationPresent(MyAutowired.class)){
                    MyAutowired autowired = field.getAnnotation(MyAutowired.class);
                    //根据有没有指定注入类型判断是属性类型注入还是指定注入类型注入
                    String beanName = autowired.value();
                    if(beanName == null ||beanName.isEmpty()){
                        //根据字段类型全局限定名注入 eg:com.gupaoedu.demo.service.IDemoService
                        beanName = field.getType().getName();
                    }
                    field.setAccessible(true);
                    //根据反射注入值
                    try {
                        field.set(entry.getValue(), ioc.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }

//                }
            }

        }

    }

    //简单工厂
    private void doInitIOC() {
        if(classNames.isEmpty()){return;}

        try {
            for (String className:classNames){
                Class<?> clazz = Class.forName(className);
                if(clazz.isAnnotationPresent(MyController.class)){
                    String  beanName = clazz.getSimpleName();
                    ioc.put(toLowerFirstCase(beanName), clazz.newInstance());
                }else if(clazz.isAnnotationPresent(MyService.class)){
                    //beanName默认值
                    MyService service = clazz.getAnnotation(MyService.class);
                    String beanName = service.value();
                    if(beanName ==null || beanName.isEmpty()){
                        beanName = clazz.getSimpleName();
                    }
                    Object o = clazz.newInstance();
                    ioc.put(toLowerFirstCase(beanName), o);
                    //3、根据类型注入实现类，投机取巧的方式
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for(Class<?> i :interfaces){
                        if(ioc.containsKey(i.getName())){
                            throw new Exception("The beanName is exists!!");
                        }
                        ioc.put(i.getName(), o);
                    }
                }else{
                    continue;
                }


            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    private void doScanPackage(String scanBasePackage) {
        URL resource = this.getClass().getClassLoader().getResource("/" + scanBasePackage.replaceAll("\\.","/"));
        File rootFile = new File(resource.getFile());
        for(File file:rootFile.listFiles()){

            if(file.isDirectory()){
                doScanPackage(scanBasePackage + "." + file.getName());
            }else{
                if(!file.getName().endsWith(".class")){continue;}
                String className = (scanBasePackage + "." +file.getName()).replace(".class","");
                classNames.add(className);
            }
        }
    }

    private void doLoadConfig() {
        try {
            properties.load(this.getClass().getClassLoader().getResourceAsStream(this.getServletConfig().getInitParameter("contextConfigLocation")));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String toLowerFirstCase(String simpleName) {
        char [] chars = simpleName.toCharArray();
        chars[0] += 32;
        return  String.valueOf(chars);
    }
}
